Status legend ✅ Documented — taken directly from the Figma frames / spec table. 🟡 Assumed — a reasonable rule I've proposed to make the prototype compute; needs your confirmation. 🔴 Open — not yet specified anywhere; decision required.
Last updated from:
Key user flows,Goal Setting Flow,WeeklyLossRate,CutStrategy,MenstrualCycleWeightChanges,Trending Weight & Phases, the per-sportGS_14A_*frames, and the sport rules table.
OnWeight helps a combat athlete cut to their fight-day weight safely and on schedule. The app's job is to interpret daily weigh-ins — telling the athlete in plain language whether they are on track, then exposing the underlying numbers for those who want them.
The core intelligence is three layers:
| Field | Source | Notes |
|---|---|---|
biologicalSex | ✅ SignUp_BiologicalSex | Gates menstrual-cycle flow + some strategy warnings. Also selects the division ladder (divisions.men / divisions.women) via recommendDivision(sport, wt, sex) — map female → 'women', else 'men'. |
currentWeight (kg) | ✅ SignUp_CurrentWeightInput | Starting / walk-around weight. |
goalWeight (kg) | ✅ SignUp_GoalWeightInput | The athlete's target fight-day body weight. User-facing term is now "your limit" — the weight you must hit on the scale (the onboarding screen reads "Your limit", not "Goal weight"). The field / code name stays goalWeight; only the athlete-facing copy changed. See the Lexicon note in §18. |
sport | ✅ SignUp_SelectSport | Boxing / Muay Thai / BJJ (gi/no-gi) / Taekwondo / Judo / MMA. |
hasFight + fightDate | ✅ SignUp_Date_Fight / SignUp_NoFight | No-fight users get an open-ended plan. |
weighInPreference (time of day) | ✅ GS_02_WeighInTimePreference | When the daily fasted weigh-in reminder fires. |
morningNotif | ✅ GS_03_MorningNotifOptIn | Opt-in to the reminder. |
cutRate (% bw/week) | ✅ WeeklyLossRate | See §5. |
cutStrategies[] | ✅ CutStrategy | Fight-week tactics. See §8. |
| Cycle settings | ✅ GS_09A–GS_13A | See §7. |
Source: ✅ sport rules table + GS_14A_CheckSportDetails_*, GS_15A–GS_21A.
| Sport | Equipment req. | Equipment weight | Re-weigh-in | Hydration clause | Hydration cap | Weigh-in timing |
|---|---|---|---|---|---|---|
| Boxing | no | n/a | no | no | n/a | Same day (amateur), day before (pro) |
| Muay Thai | no | n/a | no | no | n/a | Same day (amateur), day before (pro) |
| BJJ (no gi) | yes | 0.5 kg (rashguard + pants) | no | no | n/a | Within 2 hours |
| BJJ (gi) | yes | 1.2 kg | no | no | n/a | Within 2 hours |
| Taekwondo | yes | 1.2 kg | yes (5%) | no | n/a | Day before (with re-weigh-in 5%) |
| Judo | yes | 1.2 kg | yes (5%) | no | n/a | Day before (with re-weigh-in 5%) |
| MMA | no | n/a | no | no | n/a | Day before |
Note (✅): any non-equipment sport weighs in in underwear only. Hydration-clause fields exist in the model (
GS_20A/GS_21A) for promotions that test hydration, even though none of the default sports above enable it.
entries[]: { date, weight } — one fasted morning weigh-in per day (✅ AW_01–AW_04).The number the athlete must hit on the scale is not always their goal body weight — equipment and weigh-in timing change it.
`` scaleTarget = divisionLimit // the official class limit bodyTarget = scaleTarget − equipmentWeight // what the BODY must weigh (equipmentWeight = 0 for underwear-only sports) ``
equipmentWeight.bodyTarget ≈ scaleTarget.goalWeight = 74.4 (body) and maxWithGi = 76.0 (scale, +1.6 allowance). Replace with real division limits when wired to a class table.✅ A second weigh-in caps rehydration: the athlete may not exceed +5% of the division limit at re-weigh. This limits how aggressively they can dehydrate, because they must be able to recover within the cap.
✅ Documented per sport; 🟡 implication for the cut:
🟡 Proposed rule: weigh-in timing sets the maximum safe fight-week water cut and therefore how much of the gap can be left to fight week vs. must be lost as true weight during camp.
GoalOutOfRange)When the entered limit is more than ~20 kg from current weight (either direction), the goal step raises a calm "Out of healthy range" gate instead of silently building a plan: it names the risk plainly ("This target looks like a mistake"), points the athlete to a doctor + sports dietitian, and states that OnWeight won't build an aggressive plan toward an unsafe target. A "Keep it anyway" branch lets the athlete proceed against advice (they own the number), but the recommendation and tone stay conservative. Within ±20 kg there is no popup. Lives in fc-strategy-info.jsx; see §18.
Daily weigh-ins are noisy (water, food, glycogen). The displayed trend is a 5-day exponentially-weighted moving average:
`` trend[i] = Σ ( weight[i-k] · 0.75^k ) / Σ ( 0.75^k ) for k = 0..4 ``
trendingToday = the most recent trend value (today's "true" weight).`` weeklyVelocity = 7-day change in the trend // kg/week, negative = losing dailyVelocity = weeklyVelocity / 7 ``
`` daysToWeighIn = campDays − todayDay projected = trendingToday + dailyVelocity × daysToWeighIn // TRUE-weight projection gap = projected − bodyTarget // + = will be over the limit toGo = trendingToday − bodyTarget // kg still to lose from today ``
🟡 Currently a straight-line extrapolation of current pace. A more honest model would project along the planned rate (the chosen
cutRate) and flag divergence — see §6.
The number that matters is what the scale reads on weigh-in day, not the athlete's underlying true weight. If weigh-in lands in a water-retention phase (luteal), the scale reads higher than true weight — so the plan must pre-cut extra true weight as a buffer.
`` weighInOffset = cycleOffsetKg(weighInDay) // §7 — kg the scale reads above true weight that day projectedScale = projected + weighInOffset // honest scale reading at weigh-in cycleBuffer = max(0, weighInOffset) // extra TRUE weight to pre-cut trueTarget = bodyTarget − cycleBuffer // the true-weight number the plan aims at scaleGap = projectedScale − bodyTarget // the gap that actually decides made/missed ``
weighInOffset, so the honest "could I be over?" risk is visible. Verdict: cone clears / straddles / misses the limit. This replaces the over-confident single dashed line (addresses F4).cycleOffsetKg(day), dipping in follicular and rising in luteal, but always landing on bodyTarget at weigh-in. The true-weight target sits cycleBuffer below it.weighInOffset ≈ +0.5 kg, so trueTarget = 74.4 − 0.5 = 73.9 kg. The athlete must trend to 73.9 true so the scale shows 74.4. (Default amplitude is now 0.5 kg — see §7; personalised upward from logged data, e.g. the +0.8 kg in the Learned from your data screen.)Source: WeeklyLossRate. Rates are % of body weight per week:
| Option | Rate | Guidance |
|---|---|---|
| Maximum | 1.0% / wk | Beyond this, muscle loss is likely, impacting strength & power. (Hard ceiling.) |
| Aggressive | 0.75% / wk | Effective but may impact performance during camp. |
| Recommended | 0.5% / wk | Optimal — preserves muscle, supports strong performance. |
| Conservative | 0.25% / wk | Slow & steady; minimal performance impact, needs a longer timeline. |
`` plannedWeeklyLossKg = currentWeight × (cutRate / 100) ``
1.0%/wk is the safety ceiling — sustained loss faster than this = "cutting too fast".(currentWeight − bodyTarget) can't be reached by the fight date at ≤1%/wk, warn the athlete (timeline too short / consider a higher class).Source: Trending Weight & Phases shows a 4-zone gauge: Low · On Track · High · Risk, evaluated on the 7-day average. This maps to the four feedback screens (AW_08A–AW_8D).
| Gauge zone | Meaning | Feedback screen | Tone |
|---|---|---|---|
| On Track | Loss rate ≈ planned; projection lands at/under target | AW_08A_OnTrack | green |
| Low | Losing too slowly; projection misses target but recoverable | AW_08C_SlowProgress | amber |
| Risk (slow) | So far behind it isn't safe to make weight at ≤1%/wk | AW_8D_NotOnTrack_CTA | red |
| High | Losing faster than planned; over-cutting | AW_08B_TooFast | amber |
🟡 Proposed thresholds (NEED CONFIRMATION). Let
r= current 7-day-avg loss as % bw/wk andtarget= chosencutRate:
- On Track:
target − 0.15% ≤ r ≤ target + 0.20%and projectedgap ≤ +0.2 kg.- Low (behind):
r < target − 0.15%and the gap is still closable by date at ≤1%/wk.- Risk (off-track): the remaining gap cannot be closed by the fight date at ≤1%/wk → CTA to change plan / move up a class.
- High (too fast):
r > target + 0.20%(and/orrapproaching/over the 1%/wk ceiling).🔴 Confirm the exact boundary numbers and whether the gauge is driven purely by rate (
r) or by projected gap, or a blend of both.
Source: GS_13A_MenstrualCycleWeightChanges + a literature review (2024–25). Shown only when biologicalSex = female and the athlete opts in (GS_09A). All values are customisable defaults; contraceptive users may set all to 0; athletes with absent/irregular cycles are handled separately (§7.4).
✅ What the evidence says (replaces the old guesses).
- The change is small, fluid-only, and absent in many women. A large systematic review (Kanellakis et al., 2023, Am. J. Human Biology) found the majority of studies report no significant body-weight/composition difference between phases.
- Where detected, the magnitude is ~0.45 kg average (full-cycle tracking), explained almost entirely by water, not fat; individual ranges run up to ~2 kg.
- Mechanism & timing are well-established: progesterone peaks mid-luteal → fluid retention; late-follicular estrogen raises extracellular fluid. Retention peaks late-luteal into the first 1–2 days of menstruation, then clears. In athletes, body mass + total body water rise from follicular → luteal.
- Implication: the old +1.0 / +2.0 kg luteal defaults overstated it, and a −1.0 kg follicular offset has no basis — follicular is baseline. Default amplitude is now 0.5 kg, and the product leads with annotation over arithmetic because the effect is so individual.
One smooth curve is the single source of truth for every cycle chart and the projection (§4.4), so they can never disagree. Water offset (kg the scale reads above true weight) follows a cosine over the cycle: it dips to −retention in follicular (~day 10) and peaks at +retention in late luteal (~day 24).
`` cycleDayAt(campDay) = (dayToday + (campDay − todayDay)) mod cycleLength cycleOffsetKg(campDay) = retention × −cos( 2π · (cycleDayAt − 10) / cycleLength ) cyclePhaseAt(campDay): <5 menstrual · <13 follicular · <16 ovulation · else luteal ``
Model offset (cosine, around the cycle mean):
| Phase | When | Model offset at phase centre | Note |
|---|---|---|---|
| Menstruation | days 0–5 | ~0 kg (falling off the luteal peak) | puffiness often lingers day 1–2 |
| Follicular | days 5–13 | −retention (≈ −0.5 kg) | water low — lightest readings |
| Ovulation | days 13–16 | ~0 kg (rising) | |
| Luteal | days 16–28 | +retention (≈ +0.5 kg) | water retention — heaviest readings |
User-facing onboarding defaults (simplified, relative to baseline, all editable): Luteal +0.5, Menstruation +0.3, Follicular 0, Ovulation 0. retention is the peak amplitude (default 0.5 kg). The slight framing difference (cosine-around-mean vs. baseline-relative phase numbers) is intentional: the curve drives the math; the phase numbers are the human-editable view.
cycleBuffer and trueTarget.cycleOffsetKg(day) so the planned scale reading rises in luteal and dips in follicular, landing on bodyTarget.nextWeekGoalKg −= Δphase_nextWeek.The per-phase amplitude is a starting estimate, then learned per athlete. Once ~2–3 cycles are logged, fit each athlete's real luteal amplitude from the cyclic residual of their weigh-ins (the swing left after removing the downward cut trend) and recommend an update (e.g. default +0.5 → measured +0.8). The athlete accepts or keeps the default — never silent.
🟡 Caveats to honour in the model: (1) needs ≥2–3 cycles; (2) the cut trend confounds the cyclic signal — must separate trend from residual; (3) round conservatively and label it a personalised estimate, not medical advice.
Hard camps and aggressive cuts frequently disrupt or stop periods. Captured as Regular / Irregular / Not currently:
✅ The prototype now uses the evidence-based default (luteal peak +0.5, follicular −0.5, amplitude
retention = 0.5), personalised from data and always overridable. This resolves the old +2.0 / +1.0 / −1.0 discrepancy and the F8 menstruation-note contradiction (menstruation now carries a small positive lingering hold, consistent with the "may rise" note).
The daily fasted weigh-in is only comparable day-to-day if it's taken at a consistent time. The app treats consistency as the priority and first-thing-fasted as the accuracy ideal.
weighInTime, weighInTargetHour)GS_02_WeighInTimePreference); editable any time from Plan → Daily weigh-in, or the "Change time" action on the Trend-tab time-drift card.weighInTargetHour is marked off-window (noisier — water/food/activity drift). Surfaced as a red time label in the log and a red dot on the time-drift scatter (Trend tab).Source: CutStrategy. These are fight-week tactics, separate from the daily camp cut rate.
| Strategy | Effect | Recommendation |
|---|---|---|
| Low Fibre Diet | Flush gut, ~1% of scale weight | Recommended |
| Low Sodium Diet | 2–3 days, ~0.5–1% water weight | Recommended |
| Sweat Out | Sauna/sweat | ⚠️ Never more than 5% of bodyweight |
| Low Carb | Glycogen depletion | ❌ Not recommended — 24 h to reload, fuels performance |
| Water Load | Manipulate water, temp. ~0.5% drop | ❌ Not recommended for female athletes |
🟡 Selected strategies define the fight-week portion of the cut (the water/manipulation buffer), which combines with the camp's true-weight loss to reach
scaleTarget. Total water-cut should respect the per-strategy ceilings (esp. Sweat Out ≤5%) and the re-weigh cap (§3.1).
✅ Documented as a flow ("Log weight → adaptive weekly goal updates → feedback"; "Edit Plan → recalculated targets") and shown per-week in Weeks (Check-ins x/7, Tracking Weight, Weekly Loss %, Average weight loss kg).
Principle: daily scale weight is mostly water/gut noise and is stressful for athletes to interpret. We never judge a cut on a raw daily reading. We escalate through timescales, and every comparison is against the cycle-adjusted expected line (§7), never a raw straight line.
The evidence that sets the thresholds. Within-subject day-to-day body-mass SD is ~0.5 % of bodyweight under standardised weighing (≈0.4 kg at 78 kg), and larger free-living (2-wk change SD ≈1.1–1.3 kg; ~630 kcal/day intake swing ≈ 1 kg). Averaging only cuts noise by √n, so:
| Window | residual noise (1 SD) | true loss in window @0.5–1 %/wk |
|---|---|---|
| 1 day | 0.4–0.8 kg | 0.06–0.11 kg |
| 3-day avg | 0.23–0.46 kg | 0.17–0.34 kg |
| 7-day avg | 0.15–0.30 kg | 0.39–0.78 kg |
→ A 3-day average's own noise is as big as the signal AND bigger than a ±200 g band, so a ±200 g pass/fail on 3 days misclassifies ~⅓–½ of the time. The signal only clears the noise at the 7-day rate (and 7-day-vs-prior-7-day). Therefore:
Step 0 — Displayed trend weight. The only weight the athlete sees as a verdict is a 7-day-equivalent smoothing (EWMA, smooth()), never the raw daily number. Raw daily is logged, charted as dots, but carries no verdict.
Step 1 — Daily silent gate. If today's reading is within ±200 g of the cycle-adjusted expected weight → silently "fine", stop. Off it → escalate silently (no alarm, no red).
Step 2 — 3-day provisional filter. 3-day trailing average within ±0.4 kg (≈0.5 %, matched to its real noise — not 200 g) of the cycle-adjusted expected weight → treat as noise, on track. Else → go to the rate check.
Step 3 — 7-day rate verdict. Compare the last-7-day vs prior-7-day true-weight rate (cycle-water removed). Requires ≥4 logged days per window; with <14 days of data, fall back to an all-data regression. This rate drives the on-track / cutting-fast / behind verdict.
Symmetry. Being under the line (losing too fast) flags the same way as being over — but the response differs: too-fast → add food back; behind → tighten. (Voice per §11.)
A "Change your plan" flow launches when either:
Anti-flapping: separate enter/exit thresholds and a 2-reading confirmation before the flow is offered, so the athlete is never bounced in and out of "off track".
Each week, re-derive the remaining required pace from the current trend so the plan self-corrects: `` weeksLeft = daysToWeighIn / 7 requiredWeekly = (trendingToday − trueTarget) / weeksLeft // trueTarget already cycle-buffered (§4.4) nextWeekGoalKg = clamp(requiredWeekly, 0, currentWeight × 1%) // never exceed the 1%/wk ceiling nextWeekGoalKg −= Δphase_nextWeek // cycle offset (§7) ` If requiredWeekly` would exceed the 1 %/wk ceiling, don't silently cap and carry on — fire re-plan trigger (A). Missed check-ins: a window with <4 logs holds the prior verdict rather than guessing.
Population default water amplitude = 0.5 kg (evidence: ~0.45–0.5 kg average premenstrual gain, fluid not fat; many women see none, some up to ~1.4–2.3 kg). Individualised from the athlete's own logged residuals (§15.4), learned upward or downward. Never assume a large swing for everyone.
The problem the dietitian raised: a 7-day rate is statistically sound but slow — for a fighter on a tight deadline, "by day 7 it's too late." Lost runway can't be recovered.
The resolution (not a binary): a 7-day window is rolling — it updates every day, not a checkpoint you wait for; and we layer a faster early-warning tier on top. Detection theory is clear there's an unavoidable trade between detection delay and false-alarm rate, so we place that trade deliberately: early + soft (high sensitivity, off the alarm channel) → confirmed + hard (high specificity, drives the re-plan flow).
| Tier | Method | Speed | Surfaced as | Can fire re-plan? |
|---|---|---|---|---|
| Trend | EWMA (smooth()) — recent days weighted heaviest | bends in ~2–3 days | the only weight shown | no |
| Drift-watch | CUSUM of daily deviation vs cycle-adjusted line (driftWatch()) | catches consistent drift in ~2–3 days | quiet, non-red note | no (soft only) |
| Confirmed verdict | 7-day rolling rate + daily projection (§9.1, §9.2) | confirmed, low false-alarm | the verdict + Change-plan | yes |
Why EWMA, not a flat average. An EWMA "will detect shifts of 0.5σ to 2σ much faster than a simple chart with the same sample size" because recent data is weighted heaviest. Responsiveness is tunable via the smoothing factor (β high = smoother/slower, β low = faster/noisier).
Why CUSUM for the early tier. A one-sided cumulative sum S = max(0, S + (deviation − k)) accumulates small same-direction deviations "to achieve the effect of amplification," making a persistent drift visible "much faster" than a windowed average — while the slack k (~0.15 kg/day) absorbs ordinary water noise so a single bad-water morning never trips it. It is soft-only: it opens a watch, never the re-plan flow.
Deadline-tightening (context-driven). The CUSUM decision limit h shrinks as fight day nears (h = 0.85 → 0.65 → 0.45 kg at >28 / ≤28 / ≤14 days out). A fighter's costs are asymmetric — a missed "you're behind" is far worse than an extra soft nudge — so we buy faster detection late, when lost runway is most expensive, at the price of a few more (still non-alarming) watches.
Two protections that matter more than raw detection speed:
Symmetry. Drift-watch runs two-sided: behind (losing too slow) and fast (losing too quick) trip independently, each with its own supportive response (§11).
The app is paywalled end to end — there is no free tier and no Pro tier. First-run is therefore one funnel with one hinge: the paywall. Before it, the job is convert; after it, activate. (The earlier Free-Onboarding / Pro-Goal-Setting tier split is retired — those tiers don't exist.) See OnWeight - Onboarding & Goal Setting Flows.html for the strategy + visual map (the prior tier-based map is preserved as …Flows v1.html); live screens are in OnWeight - All Screens.html.
Flow 1 · Onboarding (pre-paywall) — prove value, earn the subscription (eyebrow "OnWeight Setup"). Six high-signal questions, each reshaping the plan; account & permissions deferred.
Launch → Sport → (Fight date | No fight) → Biological sex → Current weight → Goal weight → Cut rate → Building your camp → Coach's Read → Plan Preview · Paywall
Each pre-paywall question must change the plan or be cut. The Coach's Read is the engineered "aha" before price; the paywall delivers a plan the athlete watched build, headline echoing their own numbers. The hinge: subscribe → create account to save the camp → setup. A hard paywall has no free-home fallback — declining ends the session, so re-engagement (reminder / email / win-back) is the only recovery path and must be built. (Trial vs hard-paid: still open — recommend a free trial; longer trials convert materially better.)
Flow 2 · Set up camp (post-paywall) — precision, then a first win (eyebrow "Set up your camp", no tier pill). Friction acceptable; the new-user experience runs to the first logged weigh-in. `Weigh-in time → Morning reminder (push opt-in, deferred) → [if female & opted-in] Cycle on? → Period date → Cycle length → Phase weight changes → Confirm competition rules (equipment, equipment weight, re-weigh-in + buffer, hydration clause/cap) → Cut strategies → Check details → Refining plan →
Choose plan shape (Tapered | Linear) → Log first weigh-in → Home`
Cut strategies moved here (fight-week detail, not a conversion lever). Account creation happens at purchase; the notification permission is deferred to Step 2. The cycle sub-flow is gated on
biologicalSex = female(captured pre-paywall) and the opt-in; otherwise it skips straight to competition rules. Every cycle screen is independently skippable. Ends on the first weigh-in — the activation milestone that predicts retention.
Log weight (Home quick action or Logbook) Enter weight → pick date → Save → recompute trend + projection → adaptive weekly goal update → Feedback screen (On Track / Too Fast / Slow / Off-track CTA)
Edit Plan Change fight date / weigh-in / division / equipment / cycle settings → recalculated targets
Browse Learn → watch video / read article → (optional) save.
View Strategy Guidance → what's generally recommended / NOT for your sport & weigh-in timing.
When wiring the prototype to this spec:
PACE_STATES presets with a classifier that takes one weight series + cutRate and derives the gauge zone via §6 thresholds.retention = 0.5 kg (§7/§14.3), personalised from logged data and always overridable. The old +2.0/−1.0 guesses and the "§7 is OPEN" hold are retired.cutRate (§5) and per-sport rules (§2.2) driving bodyTarget (§3).These were parked; all six are now answered against the literature + governing-body rules in §14. The values there are evidence-based defaults, still user-overridable.
Things that are internally inconsistent, ambiguous, or look risky as written. None are blocking, but they should be resolved before this logic is trusted in a build.
trueTarget = scaleTarget), selected automatically by weigh-in timing.retention amplitude (0.5 kg), replacing the contradictory per-phase constants. Still an unvalidated amplitude.equipmentWeight to get the body target, underwear-only sports use it directly.hydrationCap never engages. Fine to keep for other promotions, but confirm it's intentionally unused rather than missing data.The open questions in §12 and several flags in §13 are resolved below against sports-science literature and governing-body rules. Numbers are defaults, not medical advice — every value stays user-overridable, but these are the evidence-based starting points.
Sources (combat-sport weight-cutting science): Reale, Slater & Burke, Acute-weight-loss / rapid-weight-loss reviews (IJSNEM); Ruiz-Castellano et al. 2021, Achieving an Optimal Fat Loss Phase in Resistance-Trained Athletes (Nutrients); Hagmar et al. & Stachenfeld, menstrual-cycle fluid balance; ACSM/IOC weight-management position stands; governing bodies: IJF SOR, World Taekwondo, IBJJF, UFC / ABC Unified Rules, IBA / USA Boxing.
plannedWeeklyLossKg = currentTrendWeight × cutRate%.too slow ← on-plan → too fast, with a hardred zone at ≥ 1.0 %/wk.
r = 7-day loss %bw/wk, target = chosen cutRate.target−0.15 ≤ r ≤ target+0.20 and projection cone clears the limit.target+0.20 < r < 1.0.r ≥ 1.0 (the ceiling — muscle-loss zone).≤ 1.0 %/wk.≤ 1.0 %/wk → feasibility CTA (§14.6). requiredWeekly = (trendingToday − trueTarget) / weeksLeft nextWeekGoalKg = clamp(requiredWeekly, 0, currentTrendWeight × 1.0%) − Δphase_nextWeek ``requiredWeekly (and may trip the feasibility CTA).FC.cycle.retention = 0.5 — see §7/§9.4 — because the average cyclic swing is ~0.45 kg, fluid not fat, and many athletes see none. 1.0 kg was the original guess. ⚠️ Note one build inconsistency to fix: the Learned-from-your-data card still labels the population "typical" as +1.0 kg (LEARNED_CYCLE.populationPeakKg) while the default amplitude is 0.5 — align these.) Refinement: shift the modelled peak to late luteal / menses onset (it currently peaks ~day 24; move toward day 26–28) and the trough to mid-follicular (~day 8–10). The shape is validated; only the peak position needs the nudge.scaleTarget) instead of typing a blind goal weight. Maintain one table per org (Boxing, MMA/UFC, IBJJF, IJF, WT, Muay Thai).divisionLimit − equipmentWeight` (the body must come in lighter).
bodyTarget ≈ divisionLimit.trueTarget = scaleTarget, the whole cut is true-weight loss across camp at ≤ 1 %/wk. This is a different plan type from the camp-cut-plus-water model, selected automatically by weigh-in timing.At onboarding and on every plan edit: `` requiredAvgWeekly = (currentWeight − bodyTarget) / weeksToWeighIn // off current weight infeasible = requiredAvgWeekly > currentWeight × 1.0% // can't be done safely veryHighCut = (currentWeight − bodyTarget) / currentWeight > 0.10 // >10% total — flag risk ``
The cycle work is bigger than one water-offset number. It spans who has a cycle to model, a health safeguard, where you are right now, symptom logging, learning from past months, and education. Built on the menstrual-cycle science (§7/§14.3) plus the sources below.
Sources: Elliott-Sale et al. (cycle methodology); Menstrual Cycle and Hormonal Contraceptives in Female Athletes: Should Symptoms and Nutrition Matter More than Cycle Phase? (Nutrients 2026, doi 10.3390/nu18071144); IOC RED-S consensus (2014/2018/2023); ACSM Female Athlete Triad; PMC9724109 (RED-S female athlete); PMC7937612 / PMC8281678 (MC & contraception vs strength).
Guiding principle (from the 2026 review): individual, logged symptom & weight patterns should outweigh textbook phase math. Generic phase recommendations are applied **cautiously, and only once a consistent personal pattern is documented.* This is the spine of §15.4 (learning).
The phase model only applies to one of these. Captured at GS_09A, editable in Cycle settings.
| Mode | Who | What the app does |
|---|---|---|
| Natural cycle | Eumenorrheic, no hormonal contraception | Full phase model (§7.1): water offset, undulating plan, buffer. Refined by learning (§15.4). |
| Hormonal contraception | Pill / patch / ring / hormonal IUD / implant / injection | No natural-phase model — HC suppresses endogenous fluctuation. Default flat (0 offset). If the method has a withdrawal/placebo week the user can flag it; otherwise rely on logged symptoms only. Effects are modest & highly individual — never assume. |
| Absent / irregular | No period, post-menopause, pregnancy, or irregular | No phase model. If "absent" is unexpected → RED-S safeguard (§15.2). Irregular cycles fall back to symptom + learned patterns, not calendar prediction. |
🟡 Default offset for HC and Absent modes is 0 kg until the athlete's own data says otherwise.
Combat & weight-category sports carry elevated RED-S risk (low energy availability), and losing your period is a primary warning sign, not a convenience for the model.
note: a lost/irregular period during a hard cut can signal low energy availability (RED-S).
day X / length), and the plain-language read ("late luteal — expect to hold ~+1 kg water"). One glance, interpretation-first.than pure prediction) and resets the amenorrhea counter.
Rather than holding the §7 constants forever, personalise from the athlete's own logged history (weight swings aligned to confirmed cycle days, plus symptoms). This is exactly what the 2026 review argues for.
`` For each completed, confirmed cycle: alignedSwing[phase] = mean(trend-detrended weight) within that phase // remove the cut trend first learnedOffsetKg(phase) = weightedMean(alignedSwing[phase] over last N cycles) // recent cycles weighted higher learnedPeakDay = cycle day of the athlete's observed water maximum confidence = f(number of confirmed cycles, consistency of pattern) ``
N (cycles in the window), the confidence function, and the cold-start→learned blend curve.Short, coach-toned, sport-specific. Each ties to a moment in the app:
When there's no fight date, the countdown engine (projection cone, pace gauge, adaptive goal) has nothing to anchor to. Maintenance mode replaces it. Weight-only — this app is about making weight on the scale, so no body-composition input here.
Sources: ISSN 2024 position stand, Nutrition and weight-cut strategies for MMA & combat sports (PMC11894756); Weight cycling in combat sports: 25 years of evidence (BMC, PMC8670259); Weight loss in combat sports (J Int Soc Sports Nutr, 1550-2783-9-52); Physiological Perturbations: Weight Cycling & Metabolic Function (PMC10890020).
| User | Has division? | Maintenance target |
|---|---|---|
| Off-season | yes | Recommend a band from the division; they confirm/adjust. |
| No fight booked | yes | Same as off-season — stay fight-ready. |
| General / no division | no | Fully self-set goal weight or range; no division math. |
Both signals, no countdown:
readinessWeeks = max(0, (currentTrend − bodyTarget) / (currentTrend × 1%)) `` Always tells the athlete the standing cost of getting back to weight — the no-deadline analogue of the projection.Maintenance is a teaching moment between camps. Where appropriate, attach short education: the maintenance-band reasoning (16.2), the anti-cycling piece (16.4), and "what your walk-around weight says about your division" — all in the calm, coach-toned Learn style (§15.5).
The experience should feel bespoke to each sport: the weigh-in timing, equipment, re-weigh rule and the performance reason behind the cut all differ. This section is the sourced source-of-truth.
| Sport | Weigh-in timing | Equipment | Re-weigh / hydration | Plan type | Conf. |
|---|---|---|---|---|---|
| Boxing | Amateur: same-day; Pro: day before | none (underwear/shorts) | amateur uses same-day weigh-ins to curb cycling | amateur → walk-it-down; pro → camp | 🟢 |
| MMA | Day before (a.m.); some promotions add hydration tests (ONE Championship) | none | regain typically 8–15 lb / ~4–8% bw; some orgs cap/ test hydration | camp (water cut viable) | 🟢 |
| Muay Thai | No universal standard — varies by sanctioning body; pros ~24h before, amateurs (IFMA) same-day | none (fight attire) | varies; ONE ≤5% regain | free-entry goal weight; same-day → walk-it-down | 🟢 |
| BJJ (IBJJF) | Same-day, in the gi, immediately before you compete | gi (weighed dressed) | single weigh-in, no tolerance, no re-weigh | minimal water cut | 🟢 |
| Taekwondo (WT) | Day before + random same-day control | dobok (gear) | random control ≤ +5 % of category limit → DQ (WT Competition Rules Art. 9 §2.2, confirmed Jun 2026) | camp | 🟢 |
| Judo (IJF) | Day before (since 2017) + random comp-day control | judogi (gear) | random control ≤ +5 % of category limit | camp | 🟢 |
| Wrestling (UWW) | Same-day, morning of competition, in singlet; multi-day events re-weigh each morning | none (singlet) | no tolerance; same-day → almost no rehydration window | walk-it-down | 🟢 |
Sources: World Boxing Competition Rules (Nov 2024, in force 1 Jan 2025); amateur same-day weigh-in practice (World Boxing / Olympic). IJF 2017 rule change — day-before official weigh-in + random competition-day control allowing ≤5 % above the category upper limit (Ceylan/ScienceDirect S2211266925000830; PMC10484915). World Taekwondo Olympic categories (olympics.com; WT) + random weigh-in +5 % tolerance — confirmed Jun 2026 against WT Competition Rules Article 9 §2.2 ("the random weigh-in shall be conducted with plus 5% tolerance of the contestant's weight category"; random check on the morning of competition; plus-categories excluded; no 2nd attempt). UWW senior freestyle categories + same-day weigh-in (uww.org, re-confirmed Jun 2026). ISSN 2024 position stand (PMC11894756) for MMA regain & hydration.
General anchors (apply to all): safe loss 0.5–1.0 %/wk (§5/§14.1); acute fight-week water only where a rehydration window exists, ≤5 % bw (§14.5); combat & weight-class sports carry elevated RED-S / weight-cycling risk (§15.2). Per-sport emphasis:
🟢 All seven sports verified against governing-body rules / peer-reviewed literature: Boxing (World Boxing 2025), MMA (ABC Unified Rules), BJJ gi + no-gi (IBJJF, men + women), Judo (IJF 2017 + 5% control), Wrestling (UWW senior freestyle), Taekwondo (WT Olympic + +5 % random control, confirmed vs WT Competition Rules Art. 9 §2.2), Muay Thai (confirmed no universal standard → free-entry). Implementation cross-check is logged in OnWeight - Rules Verification.md.
Open items for human sign-off:
(Resolved Jun 2026: Taekwondo +5 % tolerance — confirmed vs WT Competition Rules Art. 9 §2.2. BJJ women's-gi rooster — confirmed 48.5 kg, Heavy 79.3 kg vs current IBJJF tables.)
These areas were designed after §1–§17 and are live in OnWeight - All Screens.html. They don't change the cut logic above; they make the product launch-ready. Each is documented here so this file stays the single source of truth.
The athlete-facing word for the fight-day scale number is "limit" (the weight you must hit), not "goal weight." The onboarding screen reads "Your limit / The weight you must hit on the scale," and charts label the line LIMIT. Rationale: a fighter doesn't aspire to the number, they're bound by it — "limit" carries the consequence "goal" doesn't. Code/field names stay goalWeight, bodyTarget, scaleTarget (changing them risks regressions); only display copy uses "limit." Any new copy must follow this — see OnWeight - Copy Rules.html §5 lexicon.
fc-account-safety.jsx) — launch-blockersThe returning-user / legal-gate screens onboarding never covered:
Placement: account creation happens at purchase (§10, Flow 1 hinge); the safety disclaimer and age gate are part of that account step. 🔴 Product decision: exact ordering of disclaimer vs. payment.
fc-legal.jsx)In-app, readable Terms of Service and Privacy Policy (numbered sections, including a real Health & safety clause: "OnWeight is guidance, not medical advice… you use your plan at your own discretion"), plus data rights controls — export my data and delete my account/data. Needed for App Store / Play compliance and GDPR-style requests.
fc-units.jsx + fc-imperial.jsx)A central OWUnits conversion + formatting engine so every weight renders in kg or lb from one setting — no hardcoded units. fc-imperial.jsx demonstrates the key screens in lb (US/UK athletes, and the lb-native division ladders e.g. MMA/ABC). Rule: never hardcode a unit string; format through OWUnits. Division tables stay canonical in their governing-body unit and convert for display.
fc-strategy-info.jsx) — restored from Figma GS_06A_InfoEach fight-week strategy (§8) now has a tap-through ⓘ explainer bottom sheet: Low Fibre · Low Sodium · Sweat Out · Low Carb · Water Load. Each sheet carries headline · how much can you lose? · how do you do it (food examples / dosages) · warning, with the safety ceilings stated inline (e.g. Sweat Out: never more than 5 % of body weight; stop if dizzy/faint). These restore content that had survived only as one-line card descriptions.
fc-widgets.jsx) — post-launch retention surfaceHome Screen (small + medium) and Lock Screen complications showing the cut at a glance — weight, on-track state, days/■ to weigh-in — without opening the app. Design-for-sign-off; not a logic change.
The §10 flow maps predate 18.2–18.3. When the onboarding/camp flow diagrams are next revised, splice in: account creation + safety disclaimer + age gate at the paywall hinge, and a settings → legal / data-rights branch. Tracked here so the omission is explicit, not silent.
OnWeight - BFM Audit (read).htmlA start-to-finish Built for Mars critique drove a round of fixes; all are live in the prototype and All Screens (shared JSX) unless noted, with the source-of-truth and flow maps updated to match.
Onboarding / paywall (fc-onboarding.jsx, fc-screens-b.jsx):
FC_FIGHTER_QUOTES, balanced male/female: Robert Whittaker & Kayla Harrison, all verbatim), then the cross-sport safety/authority line as the quietest footnote. The same band is reused on the Coach's Read (compact variant, no quote) via a shared ProofBand component, so social proof reads consistently across the app. Sport content keyed in FC_SOCIAL_PROOF (fc-engine.jsx), falling back to the combat-wide 60–80% figure. Full sourcing in OnWeight - Social Proof Research.md.Activation (fc-empty.jsx + prototype routing):
Daily loop (fc-screens-a.jsx):
Cycle (fc-engine.jsx):
LEARNED_CYCLE.populationPeakKg set to 0.5 kg so "typical" matches the evidence default (§14.3); the demo athlete's learned +1.4 stays, to show personalisation working.Future-state concepts (fc-future.jsx, in the All Screens "Future · sandbox" section only — never wired from a live screen, per the launch-point rule):
FutWinBack) — the re-engagement recovery the hard paywall depends on (§10).FutNextCampSmarter) — a missed weight closes on what the data says for next time (a fresh-start hook); supported, never judged.FutReminderReask) — hardens the daily-loop trigger for push-declined users, with the Home Screen widget as a no-permission backup.FutCoachView) — a coach's read-only roster of their fighters (the accountability / social layer); the athlete still owns the plan.Still open (business / content, not design): final trial length + price; annual-only vs annual+monthly; and replacing the labelled placeholders (social-proof fighters/quotes, win-back & widget pricing) with verified content before launch.
fc-engine.jsx: added the missing +90 open class (was 9 of 10 divisions) and switched to kg-only labels. Flagged Taekwondo +5 % tolerance for re-confirmation.fc-engine.jsx (divisions.women): Boxing (World Boxing), MMA (UFC/ABC + Strawweight), BJJ gi + no-gi (IBJJF), Taekwondo (WT Olympic), Judo (IJF), Wrestling (UWW women's freestyle). Muay Thai women's set is illustrative (free-entry). OnbGoalWeight now takes a sex prop so the recommender returns the correct ladder; a female-athlete Goal Weight artboard was added to the canvas.